package fr.lille.inria.adam.lifl.EcoGardeInventory.Utils;

import static fr.lille.inria.adam.lifl.EcoGardeInventory.Utils.CommonUtilities.API_KEY;
import static fr.lille.inria.adam.lifl.EcoGardeInventory.Utils.CommonUtilities.POST;
import static fr.lille.inria.adam.lifl.EcoGardeInventory.Utils.CommonUtilities.TAG;
import static fr.lille.inria.adam.lifl.EcoGardeInventory.Utils.CommonUtilities.displayMessage;
import static fr.lille.inria.adam.lifl.EcoGardeInventory.Utils.CommonUtilities.mSERVER_URL;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings.Secure;
import android.util.Log;

import com.google.android.gcm.GCMRegistrar;

import fr.lille.inria.adam.lifl.EcoGardeInventory.R;

public final class ServerUtilities {

    private static final int mMAX_ATTEMPTS = 5;
    private static final int mBACKOFF_MILLI_SECONDS = 2000;
    private static final Random mRandom = new Random();
    private static String mRESPONSE = null;
    
    
    private final static Handler mHandlerPost = new Handler(){
    	@Override
    	public void handleMessage(Message msg){
    		mRESPONSE = (String) msg.obj;
    		
    		Log.d(TAG, "RESPONSE = " + mRESPONSE);
    	}		
    };
    
    /**
     * Register this account/device pair within the server.
     *
     * @return whether the registration succeeded or not.
     */
    public static boolean register(final Context mContext, final String mRegId) {
        Log.i(TAG, "registering device (regId = " + mRegId + ")");
        
        String mServerUrl = mSERVER_URL + "/register";
        
        long mBackoff = mBACKOFF_MILLI_SECONDS + mRandom.nextInt(1000);
        
        // Once GCM returns a registration id, we need to register it in the
        // demo server. As the server might be down, we will retry it a couple
        // times.
        for (int i = 1; i <= mMAX_ATTEMPTS; i++) {
            Log.d(TAG, "Attempt #" + i + " to register");
            try {
                displayMessage(mContext, mContext.getString(
                        R.string.server_registering, i, mMAX_ATTEMPTS));
                
                doRegisterPost(mServerUrl, mContext, mRegId);                
                
                GCMRegistrar.setRegisteredOnServer(mContext, true);
                
                String mMessage = mContext.getString(R.string.server_registered);
                
                CommonUtilities.displayMessage(mContext, mMessage);
                
                return true;
            } catch (Exception e) {
                // Here we are simplifying and retrying on any error; in a real
                // application, it should retry only on unrecoverable errors
                // (like HTTP error code 503).
                Log.e(TAG, "Failed to register on attempt " + i, e);
                
                if (i == mMAX_ATTEMPTS) {
                    break;
                }
                try {
                    
                	Log.d(TAG, "Sleeping for " + mBackoff + " ms before retry");
                    Thread.sleep(mBackoff);
                } catch (InterruptedException e1) {
                    // Activity finished before we complete - exit.
                    Log.d(TAG, "Thread interrupted: abort remaining retries!");
                    
                    Thread.currentThread().interrupt();
                    
                    return false;
                }
                // increase mBackoff exponentially
                mBackoff *= 2;
            }
        }
        String mMessage = mContext.getString(R.string.server_register_error,
                mMAX_ATTEMPTS);
       
        CommonUtilities.displayMessage(mContext, mMessage);
       
        return false;
    }

    /**
     * Unregister this account/device pair within the server.
     */
    public static void unregister(final Context mContext) {
        Log.i(TAG, "unregistering device!");
       
        String serverUrl = mSERVER_URL + "/unregister";
        
        try {
            //post(serverUrl, params);
            doUnRegisterPost(serverUrl, mContext);
            
            GCMRegistrar.setRegisteredOnServer(mContext, false);
            
            String message = mContext.getString(R.string.server_unregistered);
            
            CommonUtilities.displayMessage(mContext, message);
        } catch (Exception e) {
            // At this point the device is unregistered from GCM, but still
            // registered in the server.
            // We could try to unregister again, but it is not necessary:
            // if the server tries to send a message to the device, it will get
            // a "NotRegistered" error message and should unregister the device.
            String message = mContext.getString(R.string.server_unregister_error,
                    e.getMessage());
            
            CommonUtilities.displayMessage(mContext, message);
        }
    }
    
    private static void doRegisterPost(String mEndpoint, Context mContext, String mRegistrrationId){
    	RestService mRestServiceP;
    	
    	mRestServiceP = new RestService(mHandlerPost, mContext, mSERVER_URL+"/register"); //Create new rest service for post
    	
    	mRestServiceP.addParam("deviceId", Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID)); //Add params to request
    	
    	mRestServiceP.addParam("registrationId", mRegistrrationId);
    	
    	mRestServiceP.addParam("api_key", API_KEY);
    	
    	mRestServiceP.addParam("status", "started");
                       
        try {
        	mRestServiceP.execute(POST); //Executes the request with the HTTP POST verb
			
        	Thread.sleep(3000);	        
        } catch (Exception e) {
			e.printStackTrace();
		}
    }
    
    private static void doUnRegisterPost(String mEndpoint, Context mContext){
    	RestService mRestServiceP;
    	
    	mRestServiceP = new RestService(mHandlerPost, mContext, mSERVER_URL+"/unregister"); //Create new rest service for post
    	
    	mRestServiceP.addParam("deviceId", Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID)); //Add params to request
        
        try {
        	mRestServiceP.execute(POST); //Executes the request with the HTTP POST verb
			
        	Thread.sleep(3000);	        
        } catch (Exception e) {
			e.printStackTrace();
		}
    }

    private static void post(String endpoint, Map<String, String> params)
            throws IOException {
        URL url;
        try {
            url = new URL(endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("invalid url: " + endpoint);
        }
        
        StringBuilder bodyBuilder = new StringBuilder();
        
        Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
        // constructs the POST body using the parameters
        while (iterator.hasNext()) {
            Entry<String, String> param = iterator.next();
           
            bodyBuilder.append(param.getKey()).append('=')
                    .append(param.getValue());
            
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }
        String body = bodyBuilder.toString();
        
        Log.v(TAG, "Posting '" + body + "' to " + url);
        
        byte[] bytes = body.getBytes();
        
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            
            conn.setDoOutput(true);
            
            conn.setUseCaches(false);
            
            conn.setFixedLengthStreamingMode(bytes.length);
            
            conn.setRequestMethod("POST");
            
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded;charset=UTF-8");
            // post the request
            OutputStream out = conn.getOutputStream();
            
            out.write(bytes);
            
            out.close();
            
            // handle the response
            int status = conn.getResponseCode();
            
            if (status != 200) {
              throw new IOException("Post failed with error code " + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
      }
}
